package com.boot.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
 
/**
 * @Description: okhttp连接池，多例
 * @author: ls
 */
public class OkHttpUtils {


    private static volatile OkHttpClient okHttpClient = null;
    private static volatile Semaphore semaphore = null;
    private Map<String, String> headerMap;
    private Map<String, String> paramMap;
    private String url;
    private Request.Builder request;

    private static final String USER_AGENT_KEY ="User-Agent";
    private static final String USER_AGENT_VALUE = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36";

    private static final String MEDIA_APPLICATION_JSON_UTF8 = "application/json; charset=utf-8";
    private static final String MEDIA_TEXT = "text/plain; charset=utf-8";
    private static final String MEDIA_STREAM = "application/octet-stream";


    private static volatile ConcurrentHashMap<String, OkHttpClient> okHttpClientMap = new ConcurrentHashMap<>();


    private static final int TIME_CONNECT_TIMEOUT = 15;
    private static final int TIME_WRITE_TIMEOUT = 20;
    private static final int TIME_READ_TIMEOUT = 20;
    private static final int POOL_MAX_IDLE_CONNECTIONS = 50 ;
    private static final int POOL_KEEP_ALIVE_DURATION = 300;

    private OkHttpUtils(TimeConfig timeConfig, ProxyConfig proxyConfig, ConnectionPoolConfig poolConfig) {
        String key = getClientKey(timeConfig, proxyConfig,poolConfig);
        if (!okHttpClientMap.containsKey(key)) {
            synchronized (OkHttpClient.class) {
                if (!okHttpClientMap.containsKey(key)) {
                    Proxy proxy = null;
                    // 设置代理方式
                    if(proxyConfig!=null && proxyConfig.getIp()!=null && proxyConfig.getPort()!=null){
                        proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyConfig.getIp(), proxyConfig.getPort()));
                    }
                    TrustManager[] trustManagers = buildTrustManagers();
                    okHttpClient = new OkHttpClient.Builder()
                            //设置连接超时时间
                            .connectTimeout(getTimeValueWithDefault(timeConfig==null?TIME_CONNECT_TIMEOUT:timeConfig.getConnectTimeout(),TIME_CONNECT_TIMEOUT), TimeUnit.SECONDS)
                            //写入超时时间
                            .writeTimeout(getTimeValueWithDefault(timeConfig==null? TIME_WRITE_TIMEOUT:timeConfig.getWriteTimeout(),TIME_WRITE_TIMEOUT), TimeUnit.SECONDS)
                            //从连接成功到响应的总时间
                            .readTimeout(getTimeValueWithDefault(timeConfig==null? TIME_READ_TIMEOUT:timeConfig.getReadTimeout(),TIME_READ_TIMEOUT), TimeUnit.SECONDS)
                            //跳过ssl认证(https)
                            .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
                            .hostnameVerifier((hostName, session) -> true)
                            .retryOnConnectionFailure(true)
                            .proxy(proxy)//代理ip
                            //设置连接池  最大连接数量  , 持续存活的连接
                            .connectionPool(getConnectPool(poolConfig))
                            .build();
                    addHeader(USER_AGENT_KEY, USER_AGENT_VALUE);
                    okHttpClientMap.put(key,okHttpClient);
                }
            }
        }
        okHttpClient = okHttpClientMap.get(key);
    }

    /**
     * 创建OkHttpUtils
     *
     */
    public static OkHttpUtils builder() {
        return new OkHttpUtils(null,null,null);
    }

    public static OkHttpUtils builder(TimeConfig timeConfig, ProxyConfig proxyConfig, ConnectionPoolConfig poolConfig) {
        return new OkHttpUtils(timeConfig,proxyConfig,poolConfig);
    }

   public Map<String, Object> getMonitorRunInfo(){
       HashMap<String, Object> map = new HashMap<>();
       map.put("pool connectionCount",okHttpClient.connectionPool().connectionCount());
       map.put("pool idleConnectionCount",okHttpClient.connectionPool().idleConnectionCount());
       map.put("okHttpClientMap size",okHttpClientMap.size());

       for (Map.Entry<String, OkHttpClient> entry : okHttpClientMap.entrySet()) {
           HashMap<String, Object> concurrentMap = new HashMap<>();
           concurrentMap.put("pool connectionCount",entry.getValue().connectionPool().connectionCount());
           concurrentMap.put("pool idleConnectionCount",entry.getValue().connectionPool().idleConnectionCount());
           map.put(entry.getKey(),concurrentMap);
       }
       map.put("okHttpClientMap pollList",okHttpClientMap.size());
       return map;
   }

    private ConnectionPool getConnectPool(ConnectionPoolConfig poolConfig) {
        if(poolConfig==null){
            return new ConnectionPool(POOL_MAX_IDLE_CONNECTIONS,POOL_KEEP_ALIVE_DURATION, TimeUnit.SECONDS);
        }
       return new ConnectionPool(poolConfig.getMaxIdleConnections()>65535? POOL_MAX_IDLE_CONNECTIONS:poolConfig.getMaxIdleConnections(),poolConfig.getKeepAliveDuration(), TimeUnit.SECONDS);
    }

    private int getTimeValueWithDefault(int timeOut, int defaultTimeOut) {
        return (timeOut<=0 || timeOut> 600) ? defaultTimeOut: timeOut;
    }

    /**
     * 用于异步请求时，控制访问线程数，返回结果
     *
     */
    private static Semaphore getSemaphoreInstance() {
        //只能1个线程同时访问
        synchronized (OkHttpUtils.class) {
            if (semaphore == null) {
                semaphore = new Semaphore(0);
            }
        }
        return semaphore;
    }
 

    /**
     * 添加url
     *
     * @param url 地址
     */
    public OkHttpUtils url(String url) {
        this.url = url;
        return this;
    }
 
    /**
     * 添加参数
     *
     * @param map   参数
     */
    public OkHttpUtils addParamMap( Map<String, String> map) {
        if (paramMap == null) {
            paramMap = new LinkedHashMap<>(16);
        }
        paramMap.putAll(map);
        return this;
    }
    /**
     * 添加请求头
     *
     * @param key   参数名
     * @param value 参数值
     */
    public OkHttpUtils addHeader(String key, String value) {
        if (headerMap == null) {
            headerMap = new LinkedHashMap<>(16);
        }
        headerMap.put(key, value);
        return this;
    }

    /**
     * 添加请求头
     *
     * @param map   参数名
     */
    public OkHttpUtils addHeader(Map<String, String> map) {
        if (headerMap == null) {
            headerMap = new LinkedHashMap<>(16);
        }
        headerMap.putAll(map);
        return this;
    }
 
    /**
     * 初始化get方法
     *
     */
    public OkHttpUtils get(HashMap<String, String> map) throws Exception {
        addParamMap(map);
        request = new Request.Builder().get();
        StringBuilder urlBuilder = new StringBuilder(url);
        if (paramMap != null) {
            urlBuilder.append("?");
            try {
                for (Map.Entry<String, String> entry : paramMap.entrySet()) {
                    urlBuilder.append(URLEncoder.encode(entry.getKey(), "utf-8")).
                            append("=").
                            append(URLEncoder.encode(entry.getValue(), "utf-8")).
                            append("&");
                }
            } catch (Exception e) {
                throw e;
            }
            urlBuilder.deleteCharAt(urlBuilder.length() - 1);
        }
        request.url(urlBuilder.toString());
        return this;
    }

    public OkHttpUtils postString(String encodeString) {
        RequestBody requestBody = RequestBody.create(MediaType.parse(MEDIA_TEXT), encodeString);
        request = new Request.Builder().post(requestBody).url(url);
        return this;
    }

    public OkHttpUtils postJson(JSONObject jsonObject) {
        RequestBody requestBody = RequestBody.create(MediaType.parse(MEDIA_APPLICATION_JSON_UTF8), jsonObject==null ? new JSONObject().toJSONString():jsonObject.toJSONString());
        request = new Request.Builder().post(requestBody).url(url);
        return this;
    }

    public OkHttpUtils postMap(Map<String, String> parameters) {
        FormBody.Builder formBody = new FormBody.Builder();
        if (parameters != null) {
            parameters.forEach(formBody::add);
        }
        RequestBody requestBody = formBody.build();
        request = new Request.Builder().post(requestBody).url(url);
        return this;
    }

    public OkHttpUtils upload(String fileName,Map<String, Object> parameters) {
        MultipartBody.Builder multiRequestBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);;
        if (parameters != null) {
            parameters.forEach((k, v) -> {
                if (v instanceof File) {
                    final File file = (File) v;
                    multiRequestBuilder.addFormDataPart(k, fileName, RequestBody.create(MediaType.parse(MEDIA_STREAM), file));
                } else {
                    multiRequestBuilder.addFormDataPart(k, v.toString());
                }
            });
        }
        RequestBody requestBody = multiRequestBuilder.build();
        request = new Request.Builder().post(requestBody).url(url);
        return this;
    }

 
    public OkHttpUtils put() {
        String json = "";
        if (paramMap != null) {
            json = JSON.toJSONString(paramMap);
        }
        RequestBody requestBody = RequestBody.create(MediaType.parse(MEDIA_APPLICATION_JSON_UTF8), json);
 
        request = new Request.Builder().put(requestBody).url(url);
        return this;
    }
 
    public OkHttpUtils del() {
        String json = "";
        if (paramMap != null) {
            json = JSON.toJSONString(paramMap);
        }
        RequestBody requestBody = RequestBody.create(MediaType.parse(MEDIA_APPLICATION_JSON_UTF8), json);
        request = new Request.Builder().delete(requestBody).url(url);
        return this;
    }
 
    /**
     * 同步请求
     *
     */
    public HttpResult sync() {
        setHeader(request);
        try {
            Response response = okHttpClient.newCall(request.build()).execute();
            if(response.body()==null){
                return new OkHttpUtils.HttpResult(false,"response body is null");
            }
            return new OkHttpUtils.HttpResult(true,response.body().string());
        } catch (IOException e) {
            return new OkHttpUtils.HttpResult(false,"请求失败：" + e.getMessage(),e);
        }
    }
 
    /**
     * 异步请求，有返回值
     */
    public HttpResult async() {
        OkHttpUtils.HttpResult httpResult = new OkHttpUtils.HttpResult();
        StringBuilder buffer = new StringBuilder("");
        setHeader(request);
        okHttpClient.newCall(request.build()).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                buffer.append("请求出错：").append(e.getMessage());
                httpResult.setSuccess(false);
                httpResult.setMsg(buffer.toString());
            }
 
            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                if(response.body() != null) {
                    buffer.append(response.body().string());
                }
                httpResult.setSuccess(true);
                httpResult.setMsg(buffer.toString());
                getSemaphoreInstance().release();
            }
        });
        try {
            getSemaphoreInstance().acquire();
        } catch (InterruptedException e) {
            httpResult.setSuccess(false);
            httpResult.setMsg(buffer.toString());
            httpResult.setE(e);
        }
        return httpResult;
    }
 
    /**
     * 异步请求，带有接口回调
     *
     * @param callBack 回调
     */
    public void async(ICallBack callBack) {
        setHeader(request);
        okHttpClient.newCall(request.build()).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                callBack.onFailure(call, e.getMessage());
            }
 
            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                assert response.body() != null;
                callBack.onSuccessful(call, Objects.requireNonNull(response.body()).string());
            }
        });
    }
 
    /**
     * 为request添加请求头
     *
     * @param request 请求
     */
    private void setHeader(Request.Builder request) {
        if (headerMap != null) {
            try {
                for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                    request.addHeader(entry.getKey(), entry.getValue()==null? null:entry.getValue().toString());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
 
 
    /**
     * 生成安全套接字工厂，用于https请求的证书跳过
     *
     * @return SSLSocketFactory
     */
    private static SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) {
        SSLSocketFactory ssfFactory = null;
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            ssfFactory = sc.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ssfFactory;
    }
 
    private static TrustManager[] buildTrustManagers() {
        return new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }
 
                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }
 
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[]{};
                    }
                }
        };
    }



    /**
     * 自定义一个接口回调 - 发送异步请求时可以实现这个接口
     */
    public interface ICallBack {
        /**
         * 接口正常调用返回的内容
         *
         * @param call 回调
         * @param data 返回数据
         */
        void onSuccessful(Call call, String data);
 
        /**
         * 接口错误返回的内容
         *
         * @param call     回调
         * @param errorMsg 错误信息
         */
        void onFailure(Call call, String errorMsg);
 
    }


    private String getClientKey(TimeConfig timeConfig, ProxyConfig proxyConfig, ConnectionPoolConfig poolConfig) {
        return getTimeConfigKey(timeConfig)+ "_" + getProxyKey(proxyConfig)+getPoolConfigKey(poolConfig) ;
    }

    private String getTimeConfigKey(TimeConfig timeConfig) {
        return timeConfig==null ?"null":timeConfig.getConnectTimeout()  + "_" + timeConfig.getWriteTimeout()+ "_" + timeConfig.getReadTimeout();
    }

    private String getProxyKey(ProxyConfig proxyConfig) {
        return proxyConfig == null ? "null" :  proxyConfig.getIp()+proxyConfig.getPort();
    }

    private String getPoolConfigKey(ConnectionPoolConfig timeConfig) {
        return timeConfig==null?null:timeConfig.getMaxIdleConnections() + "_" + timeConfig.getKeepAliveDuration();
    }


    public static class TimeConfig {

        private int connectTimeout;
        private int writeTimeout;
        private int readTimeout;

        public TimeConfig(int connectTimeout, int writeTimeout, int readTimeout) {
            this.connectTimeout = connectTimeout;
            this.writeTimeout = writeTimeout;
            this.readTimeout = readTimeout;
        }

        public int getConnectTimeout() {
            return connectTimeout;
        }

        public int getWriteTimeout() {
            return writeTimeout;
        }

        public int getReadTimeout() {
            return readTimeout;
        }
    }


    public static class ConnectionPoolConfig {
        private int maxIdleConnections;
        private int keepAliveDuration;

        public ConnectionPoolConfig(int maxIdleConnections, int keepAliveDuration) {
            this.maxIdleConnections = maxIdleConnections;
            this.keepAliveDuration = keepAliveDuration;
        }

        public int getMaxIdleConnections() {
            return maxIdleConnections;
        }

        public int getKeepAliveDuration() {
            return keepAliveDuration;
        }

    }

    private static class ProxyConfig {
        private String ip;
        private Integer port;

        public ProxyConfig(String ip, Integer port) {
            this.ip = ip;
            this.port = port;
        }

        public String getIp() {
            return ip;
        }

        public Integer getPort() {
            return port;
        }
    }

    public class HttpResult implements Serializable {

        private static final long serialVersionUID = -7932267115746602337L;
        private boolean success;
        private String msg;
        private Exception e;

        public HttpResult(boolean success, String msg) {
            this.success = success;
            this.msg = msg;
        }
        public HttpResult(boolean success, String msg,Exception e) {
            this.success = success;
            this.msg = msg;
            this.e = e;
        }

        public boolean isSuccess() {
            return success;
        }

        public void setSuccess(boolean success) {
            this.success = success;
        }

        public String getMsg() {
            return msg;
        }

        public void setMsg(String msg) {
            this.msg = msg;
        }

        public Exception getE() {
            return e;
        }

        public void setE(Exception e) {
            this.e = e;
        }

        public HttpResult() {

        }
    }

}